#!/bin/bash
# /**
# * Copyright (c) 2018, yzfcer@163.com
# * All rights reserved.
# * @file    replace.sh
# * @brief   替换指定目录下符合规则的文件
# * @author  Jason Zhou,yzfcer@163.com
# * @version 1.0
# * @date    2021-09-15
# */


# Function to display help information
display_help() {
    cat <<EOF
Usage: replace.sh <directory> [options]

Options:
  -f, --filter EXT       Filter files by extension (e.g., "c|h" for C source and header files)
  -p, --prefix PREFIX    Match words with this prefix
  -s, --suffix SUFFIX    Match words with this suffix
  -w, --word WORD        Match entire word exactly
  -S, --string STRING    Match any string (not limited to word boundaries)
  -r, --replace REPLACE  Replacement string
  -d, --dry-run          Show what would be changed without making changes
  -y, --yes              Skip confirmation and apply all changes
  -h, --help             Display this help message

Examples:
  replace.sh ./src -f "c|h" --prefix "sw" --replace "swu"
  replace.sh ./docs --word "test" --replace "demo" --dry-run
  replace.sh . --string "my name" --replace "your name"
EOF
    exit 0
}

# Function to colorize output
colorize() {
    local color=$1
    local text=$2
    case $color in
        red)    echo -e "\033[31m$text\033[0m" ;;
        green)  echo -e "\033[32m$text\033[0m" ;;
        yellow) echo -e "\033[33m$text\033[0m" ;;
        blue)   echo -e "\033[34m$text\033[0m" ;;
        *)      echo -e "$text" ;;
    esac
}

# Function to build the appropriate sed pattern based on match type
build_sed_pattern() {
    local pattern=$1
    local match_type=$2

    case $match_type in
        prefix) echo "s/\\b${pattern}([^[:space:]]*\\b)/${replace}\1/g" ;;
        suffix) echo "s/\\b([^[:space:]]*)${pattern}\\b/\1${replace}/g" ;;
        word)   echo "s/\\b${pattern}\\b/${replace}/g" ;;
        string) echo "s/${pattern}/${replace}/g" ;;
        *)      echo "s/${pattern}/${replace}/g" ;;
    esac
}

# Function to perform the replacement in a single file
replace_in_file() {
    local file=$1
    local pattern=$2
    local replacement=$3
    local match_type=$4
    local dry_run=$5
    local auto_confirm=$6

    # Read the entire file content (handle empty files)
    local content
    if ! content=$(<"$file"); then
        colorize red "Warning: Failed to read file $file"
        return 1
    fi
    local original_content="$content"

    # Build the appropriate sed command
    local sed_pattern=$(build_sed_pattern "$pattern" "$match_type")

    # Apply the replacement (use GNU sed for consistent behavior)
    local modified_content
    if ! modified_content=$(echo "$content" | sed -E "$sed_pattern"); then
        colorize red "Warning: Sed replacement failed for file $file"
        return 1
    fi

    # Check if any changes were made
    if [[ "$modified_content" == "$original_content" ]]; then
        return 1  # No changes: return 1 to not count as changed
    fi

    # For dry run, show the differences
    if [[ $dry_run -eq 1 ]]; then
        echo -e "\n$(colorize blue "File: $file")"
        diff -u <(echo "$original_content") <(echo "$modified_content") | \
            sed 's/^-/'"$(colorize red "-")"'/; s/^+/'"$(colorize green "+")"'/'
        return 0
    fi

    # For actual run, show changes and confirm
    if [[ $auto_confirm -eq 0 ]]; then
        echo -e "\n$(colorize blue "File: $file")"
        diff -u <(echo "$original_content") <(echo "$modified_content") | \
            sed 's/^-/'"$(colorize red "-")"'/; s/^+/'"$(colorize green "+")"'/'

        read -n 1 -p "Apply these changes? [y/n/a/q] " answer < /dev/tty
        echo  # Newline after user input
        case $answer in
            [yY])
                echo "$modified_content" > "$file"
                colorize green "Applied changes to $file"
                ;;
            [nN])
                colorize yellow "Skipped $file"
                return 1
                ;;
            [aA])
                auto_confirm=1
                echo "$modified_content" > "$file"
                colorize green "Applied changes to $file (auto-confirm enabled for remaining files)"
                ;;
            [qQ])
                colorize red "Exiting on user request"
                exit 0
                ;;
            *)
                colorize yellow "Invalid input, skipped $file"
                return 1
                ;;
        esac
    else
        # Auto confirm, just make the changes
        echo "$modified_content" > "$file"
        colorize green "Modified $file"
    fi
    return 0  # Changes applied: return 0 to count as changed
}

# Main script execution
main() {
    # Check if no arguments were provided
    if [[ $# -eq 0 ]]; then
        display_help
        exit 1
    fi

    # Parse directory
    local dir="$1"
    shift

    if [[ ! -d "$dir" ]]; then
        echo "Error: Directory '$dir' does not exist." >&2
        exit 1
    fi

    # Initialize variables
    local filter=""
    local prefix=""
    local suffix=""
    local word=""
    local string=""
    local replace=""
    local dry_run=0
    local auto_confirm=0
    local match_type=""
    local pattern=""

    # Parse options
    while [[ $# -gt 0 ]]; do
        case $1 in
            -h|--help)
                display_help
                ;;
            -f|--filter)
                filter="$2"
                shift 2
                ;;
            -p|--prefix)
                prefix="$2"
                match_type="prefix"
                pattern="$2"
                shift 2
                ;;
            -s|--suffix)
                suffix="$2"
                match_type="suffix"
                pattern="$2"
                shift 2
                ;;
            -w|--word)
                word="$2"
                match_type="word"
                pattern="$2"
                shift 2
                ;;
            -S|--string)
                string="$2"
                match_type="string"
                pattern="$2"
                shift 2
                ;;
            -r|--replace)
                replace="$2"
                shift 2
                ;;
            -d|--dry-run)
                dry_run=1
                shift
                ;;
            -y|--yes)
                auto_confirm=1
                shift
                ;;
            *)
                echo "Error: Unknown option '$1'" >&2
                display_help
                exit 1
                ;;
        esac
    done

    # Validate parameters
    if [[ -z "$pattern" ]]; then
        echo "Error: No matching pattern specified (use --prefix, --suffix, --word, or --string)" >&2
        exit 1
    fi

    if [[ -z "$replace" ]]; then
        echo "Error: No replacement string specified (use --replace)" >&2
        exit 1
    fi

    # Escape special characters in pattern and replacement for sed
    # Handle backslashes, ampersands, slashes (sed delimiter)
    pattern=$(sed 's/[&/\]/\\&/g' <<< "$pattern")
    replace=$(sed 's/[&/\]/\\&/g' <<< "$replace")

    # Assemble find command (support multi-filetype filter like "c|h")
    local find_cmd=("find" "$dir" "-type" "f")
    if [[ -n "$filter" ]]; then
        IFS='|' read -ra extensions <<< "$filter"
        extensions=($(for ext in "${extensions[@]}"; do echo "$ext" | xargs; done | grep -v '^$'))
        
        if [[ ${#extensions[@]} -gt 0 ]]; then
            # 直接添加(作为数组元素，无需转义
            find_cmd+=("(")
            local first_ext=1
            for ext in "${extensions[@]}"; do
                if (( first_ext )); then
                    find_cmd+=("-name" "*.$ext")
                    first_ext=0
                else
                    find_cmd+=("-o" "-name" "*.$ext")
                fi
            done
            # 直接添加)作为数组元素，无需转义
            find_cmd+=(")")
        fi
    fi

    # Print configuration summary
    echo "=== Replacement Configuration ==="
    echo "Directory:    $dir"
    echo "Filter:       ${filter:-No filter (all files)}"
    echo "Match type:   $match_type"
    echo "Pattern:      $pattern"
    echo "Replacement:  $replace"
    echo "Dry run:      $([[ $dry_run -eq 1 ]] && echo "Yes" || echo "No")"
    echo "Auto confirm: $([[ $auto_confirm -eq 1 ]] && echo "Yes" || echo "No")"
    echo "Find command: ${find_cmd[*]}"
    echo "=================================="
    echo ""

    # Count processed/changed files
    local processed_files=0
    local changed_files=0

    # Process files (use -print0 to handle spaces in filenames)
    while IFS= read -r -d '' file; do
        ((processed_files++))
        if replace_in_file "$file" "$pattern" "$replace" "$match_type" "$dry_run" "$auto_confirm"; then
            ((changed_files++))
        fi
    done < <("${find_cmd[@]}" -print0)

    # Print final summary
    echo -e "\n=== Replacement Summary ==="
    colorize blue "Processed files: $processed_files"
    colorize green "Changed files:   $changed_files"
    echo "==========================="
    colorize blue "Replacement process completed."
}

# Start main function
main "$@"

